"""
--------------------------------------------------------------------------------
功能:PS2手柄底层库构建文件
日期:2025-06-06
作者:Unirobot
QQ交流群:811348489
寄语：代码中能遇见你，真好，有你，真好.
署名：有你同创智能机器人科技(北京)有限公司
--------------------------------------------------------------------------------
"""

import os
import sys
import fcntl
import struct
import select
import threading
import time
import queue  # 添加队列模块用于事件传递

class GamepadListener:
    """
    游戏手柄监听类，用于实时读取手柄输入事件
    
    使用方法：
    1. 创建实例: listener = GamepadListener(device_path='/dev/input/js0')
    2. 设置回调函数：
        - listener.set_button_callback(button_callback)
        - listener.set_axis_callback(axis_callback)
    3. 启动监听: listener.start()
    4. 停止监听: listener.stop()
    """
    
    # 默认按钮映射
    DEFAULT_BUTTON_MAP = {
        0: 'A',
        1: 'B',
        2: 'X',
        3: 'Y',
        4: 'Left Shoulder',
        5: 'Right Shoulder',
        6: 'Select',
        7: 'Start',
        8: 'Mode/Xbox',
        9: 'Left Thumb',
        10: 'Right Thumb'
    }
    
    # 默认轴映射
    DEFAULT_AXIS_MAP = {
        0: 'Left Stick X',
        1: 'Left Stick Y',
        2: 'LT',
        3: 'Right Stick X',
        4: 'Right Stick Y',
        5: 'RT',
        6: 'D-pad X',
        7: 'D-pad Y'
    }
    
    # 事件结构定义
    JS_EVENT_BUTTON = 0x01
    JS_EVENT_AXIS = 0x02
    JS_EVENT_INIT = 0x80
    
    # 事件结构大小
    EVENT_SIZE = 8
    EVENT_FORMAT = 'IhBB'
    
    def __init__(self, device_path='/dev/input/js0', 
                 button_map=None, axis_map=None,
                 poll_interval=0.1):
        """
        初始化游戏手柄监听器
        
        参数:
            device_path (str): 手柄设备路径，默认为 '/dev/input/js0'
            button_map (dict): 自定义按钮映射
            axis_map (dict): 自定义轴映射
            poll_interval (float): 轮询间隔时间（秒）
        """
        self.device_path = device_path
        self.button_map = button_map or self.DEFAULT_BUTTON_MAP
        self.axis_map = axis_map or self.DEFAULT_AXIS_MAP
        self.poll_interval = poll_interval
        
        # 回调函数
        self.button_callback = None
        self.axis_callback = None
        
        # 运行状态
        self.running = False
        self.thread = None
        
        # 按钮状态
        self.button_state = {name: 0 for name in self.button_map.values()}
        
        # 轴状态
        self.axis_state = {name: 0 for name in self.axis_map.values()}

        # 添加事件队列
        self.event_queue = queue.Queue(maxsize=100)  # 最大100个事件
        self.event_history = []  # 存储最近的事件历史
        self.max_history = 50    # 最多存储50个历史事件
        
        # 事件类型定义
        self.EVENT_BUTTON = 'button'
        self.EVENT_AXIS = 'axis'

    def set_button_callback(self, callback):
        """
        设置按钮事件回调函数
        
        回调函数格式: callback(button_name, state)
            button_name: 按钮名称
            state: 0 = 释放, 1 = 按下
        """
        self.button_callback = callback
    
    def set_axis_callback(self, callback):
        """
        设置轴事件回调函数
        
        回调函数格式: callback(axis_name, value)
            axis_name: 轴名称
            value: 轴的值
        """
        self.axis_callback = callback
    
    def start(self):
        """启动监听线程"""
        if self.running:
            print("监听器已在运行中")
            return
        
        self.running = True
        self.thread = threading.Thread(target=self._run)
        self.thread.daemon = True
        self.thread.start()
        # print(f"手柄监听器已启动，监听设备: {self.device_path}")
    
    def stop(self):
        """停止监听线程"""
        if not self.running:
            print("监听器未运行")
            return
        
        self.running = False
        if self.thread and self.thread.is_alive():
            self.thread.join(timeout=1.0)
        # print("手柄监听器已停止")
    
    def get_button_state(self, button_name):
        """
        获取指定按钮的当前状态
        
        参数:
            button_name (str): 按钮名称
            
        返回:
            int: 按钮状态 (0 = 释放, 1 = 按下)
        """
        return self.button_state.get(button_name, 0)
    
    def get_axis_value(self, axis_name):
        """
        获取指定轴的当前值
        
        参数:
            axis_name (str): 轴名称
            
        返回:
            int: 轴的当前值
        """
        return self.axis_state.get(axis_name, 0)
    
    def return_status(self, clear_queue=False):
        """
        返回当前的事件状态信息
        
        参数:
            clear_queue (bool): 是否在返回后清空队列
            
        返回:
            list: 包含所有未处理事件的列表，每个事件是一个字典
        """
        # 获取队列中的所有事件
        events = []
        while not self.event_queue.empty():
            try:
                events.append(self.event_queue.get_nowait())
            except queue.Empty:
                break
        
        # 如果需要清空队列，直接返回结果
        if clear_queue:
            return events
        
        # 否则将事件添加到历史记录并返回
        self.event_history.extend(events)
        # 保持历史记录不超过最大限制
        if len(self.event_history) > self.max_history:
            self.event_history = self.event_history[-self.max_history:]
        
        return self.event_history.copy()

    def _add_event(self, event_type, name, value, timestamp=None):
        """
        添加事件到队列和历史记录
        
        参数:
            event_type (str): 事件类型 ('button' 或 'axis')
            name (str): 按钮或轴的名称
            value: 事件值
            timestamp (float): 时间戳 (可选)
        """
        if timestamp is None:
            timestamp = time.time()
        
        event = {
            'type': event_type,
            'name': name,
            'value': value,
            'timestamp': timestamp
        }
        
        # 尝试添加到队列
        try:
            self.event_queue.put_nowait(event)
        except queue.Full:
            # 队列已满时忽略新事件
            pass
        
        # 添加到历史记录
        self.event_history.append(event)
        # 保持历史记录不超过最大限制
        if len(self.event_history) > self.max_history:
            self.event_history.pop(0)

    def _run(self):
        """监听线程的主循环"""
        try:
            # 打开手柄设备
            with open(self.device_path, 'rb') as js_device:
                # 设置非阻塞模式
                flags = fcntl.fcntl(js_device, fcntl.F_GETFL)
                fcntl.fcntl(js_device, fcntl.F_SETFL, flags | os.O_NONBLOCK)
                
                # print("手柄已连接，开始监听...")
                
                # 主监听循环
                while self.running:
                    # 使用select等待数据可读
                    rlist, _, _ = select.select([js_device], [], [], self.poll_interval)
                    
                    if js_device in rlist:
                        try:
                            # 读取事件数据
                            data = js_device.read(self.EVENT_SIZE)
                            if len(data) < self.EVENT_SIZE:
                                continue
                                
                            # 解析事件
                            timestamp, value, event_type, number = struct.unpack(self.EVENT_FORMAT, data)
                            
                            # 处理按钮事件
                            if event_type & self.JS_EVENT_BUTTON:
                                button_name = self.button_map.get(number, f"未知按钮{number}")
                                
                                # 更新按钮状态
                                self.button_state[button_name] = value
                                
                                # 触发回调
                                if self.button_callback:
                                    self.button_callback(button_name, value)
                                
                                # 添加到事件队列
                                self._add_event(self.EVENT_BUTTON, button_name, value, timestamp)
                            
                            # 处理轴事件
                            elif event_type & self.JS_EVENT_AXIS:
                                axis_name = self.axis_map.get(number, f"未知轴{number}")
                                
                                # 更新轴状态
                                self.axis_state[axis_name] = value
                                
                                # 触发回调
                                if self.axis_callback:
                                    self.axis_callback(axis_name, value)
                                
                                # 添加到事件队列
                                self._add_event(self.EVENT_AXIS, axis_name, value, timestamp)
                        
                        except OSError as e:
                            # 设备断开等错误处理
                            if e.errno == 19:  # 设备不存在
                                print("手柄设备已断开")
                                break
                            else:
                                # 其他错误，继续尝试
                                pass
        except FileNotFoundError:
            print(f"错误: 未找到设备 {self.device_path}")
        except PermissionError:
            print(f"错误: 没有权限访问 {self.device_path}")
        except Exception as e:
            print(f"监听器发生错误: {str(e)}")
        finally:
            self.running = False
            # print("监听线程已退出")

# 示例使用代码
if __name__ == "__main__":
    # 创建监听器实例
    listener = GamepadListener()
    
    # 启动监听
    listener.start()
    
    print("手柄监听器已启动，按下Ctrl+C退出...")
    print("你可以随时调用 listener.return_status() 获取事件信息")
    
    try:
        # 主循环，定期获取事件状态
        while True:
            # time.sleep(0.1)
            
            # 获取并打印所有事件状态
            events = listener.return_status(clear_queue=True)
            if events:
                print("\n=== 最近的手柄事件 ===")
                for event in events:
                    event_type = "按钮" if event['type'] == 'button' else "轴"
                    print(f"{time.strftime('%H:%M:%S', time.localtime(event['timestamp']))} "
                          f"[{event_type}] {event['name']}: {event['value']}")
                print("=====================")
            
    except KeyboardInterrupt:
        print("\n检测到Ctrl+C，正在停止监听器...")
    
    # 停止监听
    listener.stop()